package tk.mynoteapp.dev.wechat.pay.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import tk.mynoteapp.dev.wechat.pay.bean.WxPayV3Bean;
import tk.mynoteapp.dev.wechat.pay.common.AesUtil;
import tk.mynoteapp.dev.wechat.pay.common.DateTimeZoneUtil;
import tk.mynoteapp.dev.wechat.pay.common.PayHttpResponse;
import tk.mynoteapp.dev.wechat.pay.common.PayKit;
import tk.mynoteapp.dev.wechat.pay.common.RefundAmount;
import tk.mynoteapp.dev.wechat.pay.common.RefundGoodsDetail;
import tk.mynoteapp.dev.wechat.pay.common.RefundModel;
import tk.mynoteapp.dev.wechat.pay.common.WxPayApi;
import tk.mynoteapp.dev.wechat.pay.common.WxPayKit;
import tk.mynoteapp.dev.wechat.pay.enums.WxApiType;
import tk.mynoteapp.dev.wechat.pay.enums.WxDomain;
import tk.mynoteapp.dev.wechat.pay.model.Amount;
import tk.mynoteapp.dev.wechat.pay.model.Payer;
import tk.mynoteapp.dev.wechat.pay.model.UnifiedOrderModel;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/v3")
public class WxPayV3Controller {
    @Resource
    private WxPayV3Bean wxPayV3Bean;

    String serialNo;
    String platSerialNo;

    /**
     * 获取证书序列号
     * @return
     */
    private String getSerialNumber() {
        if (StrUtil.isEmpty(serialNo)) {
            // 获取证书序列号
            X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Bean.getCertPath()));
            serialNo = certificate.getSerialNumber().toString(16).toUpperCase();

//            System.out.println("输出证书信息:\n" + certificate.toString());
//            // 输出关键信息，截取部分并进行标记
//            System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
//            System.out.println("版本号:" + certificate.getVersion());
//            System.out.println("签发者：" + certificate.getIssuerDN());
//            System.out.println("有效起始日期：" + certificate.getNotBefore());
//            System.out.println("有效终止日期：" + certificate.getNotAfter());
//            System.out.println("主体名：" + certificate.getSubjectDN());
//            System.out.println("签名算法：" + certificate.getSigAlgName());
//            System.out.println("签名：" + certificate.getSignature().toString());
        }
        System.out.println("serialNo:" + serialNo);
        return serialNo;
    }

    /**
     * 取到平台序列号
     * @return
     */
    private String getPlatSerialNumber() {
        if (StrUtil.isEmpty(platSerialNo)) {
            X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Bean.getPlatformCertPath()));
            platSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();// 16进制形式
        }
        System.out.println("platSerialNo: " + platSerialNo);
        return platSerialNo;
    }

    /**
     * 保存平台证书并获取证书序列号
     * @param associatedData
     * @param nonce
     * @param cipherText
     * @param certPath
     * @return
     */
    private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
        try {
            AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
            // 平台证书密文解密
            // encrypt_certificate 中的  associated_data nonce  ciphertext
            String publicKey = aesUtil.decryptToString(
                    associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    cipherText
            );
            // 保存证书
            FileWriter writer = new FileWriter(certPath);
            writer.write(publicKey);
            // 获取平台证书序列号
            X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
            return certificate.getSerialNumber().toString(16).toUpperCase();// 16进制形式
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * 获取平台证书列表
     * @return
     */
    @RequestMapping("/get")
    @ResponseBody
    public String v3Get() {
        try {
            PayHttpResponse response = WxPayApi.v3(
                    RequestMethod.GET,
                    WxDomain.CHINA.getDomain(),
                    WxApiType.GET_CERTIFICATES.getType(),
                    wxPayV3Bean.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayV3Bean.getKeyPath(),
                    ""
            );
            // 微信支付给每个接收到的请求分配了一个唯一标示
            String requestId = response.getHeader("Request-ID");
            String timestamp = response.getHeader("Wechatpay-Timestamp");
            String nonceStr = response.getHeader("Wechatpay-Nonce");
            String serialNumber = response.getHeader("Wechatpay-Serial");
            String signature = response.getHeader("Wechatpay-Signature");

            System.out.println("timestamp: " + timestamp
                    + ", serialNumber: " + serialNumber
                    + ", nonceStr: " + nonceStr
                    + ", signature: " + signature
                    + ", requestId:" + requestId);

            String body = response.getBody();
            int status = response.getStatus();
            System.out.println("status: " + status);
            System.out.println("body: " + body);
            if (status == 200) {
                boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
                System.out.println("verifySignature: " + verifySignature);
                if (verifySignature) {
                    JSONObject jsonObject = JSONUtil.parseObj(body);
                    JSONArray jsonArray = jsonObject.getJSONArray("data");
                    JSONObject encryptObject = jsonArray.getJSONObject(0);
                    JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
                    String associatedData = encryptCertificate.getStr("associated_data");
                    String cipherText = encryptCertificate.getStr("ciphertext");
                    String nonce = encryptCertificate.getStr("nonce");
                    String serialNo = encryptObject.getStr("serial_no");
                    String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayV3Bean.getPlatformCertPath());
                    System.out.println("平台证书序列号：" + platSerialNo + " serialNo: " + serialNo);
                }

            }

            return body;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * JS API 下单 API
     * @return
     */
    @RequestMapping("/jsApiPay")
    @ResponseBody
    public String jsApiPay(HttpServletRequest request) {
        try {
            String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
            String orderId = request.getParameter("orderId"); // TODO 获取订单号，查询数据库订单数据
            UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel();
            unifiedOrderModel.setAppid(wxPayV3Bean.getAppId());// 公众号ID
            unifiedOrderModel.setMchid(wxPayV3Bean.getMchId()); // 直连商户号
            // TODO 以下数据存入数据库生成支付记录
            unifiedOrderModel.setDescription("微信支付"); // 描述
            unifiedOrderModel.setOut_trade_no(orderId); // 商户订单号
            unifiedOrderModel.setTime_expire(timeExpire); // 交易结束时间，当前设置为三分钟
            unifiedOrderModel.setAttach("微信支付"); // 支付附加数据
            unifiedOrderModel.setNotify_url(wxPayV3Bean.getDomain().concat("/v3/payNotify"));
            Amount amount = new Amount();
            amount.setTotal(1);
            unifiedOrderModel.setAmount(amount);
            Payer payer = new Payer();
            payer.setOpenid((String) request.getSession().getAttribute("openId")); // TODO 用户注册成功后，调用微信授权获取用户OpenId并存入用户表获取，此处OpenId从session中获取
            unifiedOrderModel.setPayer(payer);

            System.out.println("统一下单参数：" + JSONUtil.toJsonStr(unifiedOrderModel));

            PayHttpResponse response = WxPayApi.v3(
                    RequestMethod.POST,
                    WxDomain.CHINA.getDomain(),
                    WxApiType.JS_API_PAY.getType(),
                    wxPayV3Bean.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayV3Bean.getKeyPath(),
                    JSONUtil.toJsonStr(unifiedOrderModel)
            );
            String requestId = response.getHeader("Request-ID");
            System.out.println("统一下单响应：" + response + ", requestId: " + requestId);
            if (response.getStatus() == 200) {
                boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
                if (verifySignature) {
                    String body = response.getBody();
                    JSONObject jsonObject = JSONUtil.parseObj(body);
                    String prepayId = jsonObject.getStr("prepay_id");
                    Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayV3Bean.getAppId(), prepayId, wxPayV3Bean.getKeyPath());
                    System.out.println("唤起支付参数：" + JSONUtil.toJsonStr(map));
                    return JSONUtil.toJsonStr(map);
                }
            }
            return JSONUtil.toJsonStr(response);
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * 申请退款
     * @param orderId
     * @return
     */
    @RequestMapping("/refund")
    @ResponseBody
    public String refund(@RequestParam(required = false) String orderId) {
        // TODO 根据订单号查询订单数据
        String outRefundNo = PayKit.generateStr(); // 退款交易号
        System.out.println("商户退款单号：" + outRefundNo);
        List<RefundGoodsDetail> list = new ArrayList<>();
        RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail();
        refundGoodsDetail.setMerchant_goods_id("123");
        refundGoodsDetail.setGoods_name("测试商品");
        refundGoodsDetail.setUnit_price(1);
        refundGoodsDetail.setRefund_amount(1);
        refundGoodsDetail.setRefund_quantity(1);
        list.add(refundGoodsDetail);

        RefundModel refundModel = new RefundModel();
        refundModel.setOut_refund_no(outRefundNo);
        refundModel.setReason("测试退款");
        refundModel.setNotify_url(wxPayV3Bean.getDomain().concat("/v3/refundNotify"));
        RefundAmount refundAmount = new RefundAmount();
        refundAmount.setRefund(1);
        refundAmount.setTotal(1);
        refundAmount.setCurrency("CNY");
        refundModel.setAmount(refundAmount);
        refundModel.setGoods_detail(list);

        if (StrUtil.isNotEmpty(orderId)) {
            refundModel.setTransaction_id(orderId);
        }
        if (StrUtil.isNotEmpty(outRefundNo)) {
            refundModel.setOut_refund_no(outRefundNo);
        }
        // TODO 生成退款记录存入数据库
        System.out.println("退款参数：" + JSONUtil.toJsonStr(refundModel));
        try {
            PayHttpResponse response = WxPayApi.v3(
                    RequestMethod.POST,
                    WxDomain.CHINA.toString(),
                    WxApiType.DOMESTIC_REFUNDS.toString(),
                    wxPayV3Bean.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayV3Bean.getKeyPath(),
                    JSONUtil.toJsonStr(refundModel)
            );
            String requestId = response.getHeader("Request-ID");
            System.out.println("requestId=" + requestId);
            boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
            System.out.println("verifySignature: " + verifySignature);
            System.out.println("退款响应：" + response);
            if (verifySignature) {
                return response.getBody();
            }
        } catch (Exception e) {
            return e.getMessage();
        }

        return null;
    }

    /**
     * 支付通知
     *  支付完成后微信平台将回调此地址
     * @param request
     * @param response
     */
    @RequestMapping("/payNotify")
    @ResponseBody
    public void payNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>();
        try {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            String requestId = request.getHeader("Request-ID");
            System.out.println("timestamp=" + timestamp +
                    ", nonce=" + nonce +
                    ", serialNo=" + serialNo +
                    ", signature=" + signature +
                    ", requestId=" + requestId);
            String result = readData(request);
            System.out.println("支付通知密文：" + result);
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, wxPayV3Bean.getApiKey3(), wxPayV3Bean.getPlatformCertPath());
            System.out.println("支付通知明文：" + plainText);

            if (StrUtil.isNotEmpty(plainText)) {
                // TODO 根据订单号查询订单数据，并更新订单状态

                /*
                // 支付回调成功解密后的数据
                {
                    "transaction_id":"1217752501201407033233368018", // 商户订单号
                    "amount":{ // 金额
                        "payer_total":100,
                        "total":100,
                        "currency":"CNY",
                        "payer_currency":"CNY"
                    },
                    "mchid":"1230000109",
                    "trade_state":"SUCCESS",
                    "bank_type":"CMC",
                    "promotion_detail":[
                        {
                            "amount":100,
                            "wechatpay_contribute":0,
                            "coupon_id":"109519",
                            "scope":"GLOBAL",
                            "merchant_contribute":0,
                            "name":"单品惠-6",
                            "other_contribute":0,
                            "currency":"CNY",
                            "stock_id":"931386",
                            "goods_detail":[
                                {
                                    "goods_remark":"商品备注信息",
                                    "quantity":1,
                                    "discount_amount":1,
                                    "goods_id":"M1006",
                                    "unit_price":100
                                },
                                {
                                    "goods_remark":"商品备注信息",
                                    "quantity":1,
                                    "discount_amount":1,
                                    "goods_id":"M1006",
                                    "unit_price":100
                                }
                            ]
                        },
                        {
                            "amount":100,
                            "wechatpay_contribute":0,
                            "coupon_id":"109519",
                            "scope":"GLOBAL",
                            "merchant_contribute":0,
                            "name":"单品惠-6",
                            "other_contribute":0,
                            "currency":"CNY",
                            "stock_id":"931386",
                            "goods_detail":[
                                {
                                    "goods_remark":"商品备注信息",
                                    "quantity":1,
                                    "discount_amount":1,
                                    "goods_id":"M1006",
                                    "unit_price":100
                                },
                                {
                                    "goods_remark":"商品备注信息",
                                    "quantity":1,
                                    "discount_amount":1,
                                    "goods_id":"M1006",
                                    "unit_price":100
                                }
                            ]
                        }
                    ],
                    "success_time":"2018-06-08T10:34:56+08:00",
                    "payer":{
                        "openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
                    },
                    "out_trade_no":"1217752501201407033233368018",
                    "appid":"wxd678efh567hg6787",
                    "trade_state_desc":"支付成功",
                    "trade_type":"MICROPAY",
                    "attach":"自定义数据",
                    "scene_info":{
                        "device_id":"013467007045764"
                    }
                }
                 */

                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 退款通知
     *  退款完成后微信平台将回调此地址
     * @param request
     * @param response
     */
    @RequestMapping("/refundNotify")
    @ResponseBody
    public void refundNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>();
        try {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            String requestId = request.getHeader("Request-ID");
            System.out.println("timestamp=" + timestamp +
                    ", nonce=" + nonce +
                    ", serialNo=" + serialNo +
                    ", signature=" + signature +
                    ", requestId=" + requestId);
            String result = readData(request);
            System.out.println("退款通知密文：" + result);
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, wxPayV3Bean.getApiKey3(), wxPayV3Bean.getPlatformCertPath());
            System.out.println("退款通知明文：" + plainText);

            if (StrUtil.isNotEmpty(plainText)) {
                // TODO 根据订单号查询订单数据，并更新订单状态
                /*
                // 解密后plainText中的数据
                {
                    "mchid": "1900000100",
                    "transaction_id": "1008450740201411110005820873",// 商户订单号
                    "out_trade_no": "20150806125346", // 商户退款单号
                    "refund_id": "50200207182018070300011301001",
                    "out_refund_no": "7752501201407033233368018",
                    "refund_status": "SUCCESS",
                    "success_time": "2018-06-08T10:34:56+08:00",
                    "user_received_account": "招商银行信用卡0403",
                    "amount" : { // 退款金额
                        "total": 999,
                        "refund": 999,
                        "payer_total": 999,
                        "payer_refund": 999
                    }
                }
                */

                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取请求体中的数据
     * @param request
     * @return
     */
    private String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
